package au.com.handsunited.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import au.com.handsunited.dao.EntityDAO;
import au.com.handsunited.dao.InvitationDAO;
import au.com.handsunited.dao.MessageDAO;
import au.com.handsunited.jodawrapper.JodaDateTimeWrapper;
import au.com.handsunited.model.BaseEntity;
import au.com.handsunited.model.Group;
import au.com.handsunited.model.Organisation;
import au.com.handsunited.model.Project;
import au.com.handsunited.model.User;
import au.com.handsunited.model.invitations.Invitation;
import au.com.handsunited.model.invitations.InvitationStatus;
import au.com.handsunited.model.message.Message;
import au.com.handsunited.model.relationship.Relationship;
import au.com.handsunited.model.relationship.RelationshipStatus;
import au.com.handsunited.model.relationship.Role;
import au.com.handsunited.model.validators.EmailAddressValidator;
import au.com.handsunited.service.EntityService;

/**
 *
 * @author arunda
 */
public class EntityServiceImpl implements EntityService {

	private EntityDAO entityDAO;
	private MessageDAO messageDAO;
	protected InvitationDAO invitationDAO;

	private static Logger logger = LoggerFactory.getLogger(EntityServiceImpl.class);


	/**
	 *
	 */
	protected EntityServiceImpl() {

	}


	/**
	 * @param entityDAO
	 * @param messageDAO
	 * @param serviceCategoryDAO
	 */
	protected EntityServiceImpl(EntityDAO entityDAO, MessageDAO messageDAO, InvitationDAO invitationDAO) {
		this.entityDAO = entityDAO;
		this.messageDAO = messageDAO;
		this.invitationDAO = invitationDAO;
	}

	/**
	 * @see au.com.handsunited.service.EntityService#createEntity(au.com.handsunited.model.BaseEntity,
	 *      au.com.handsunited.model.BaseEntity)
	 */
	public BaseEntity createEntity(BaseEntity newEntity, BaseEntity owner) {

		if (EntityServiceImpl.logger.isDebugEnabled() && owner != null) {
			EntityServiceImpl.logger.debug("{}[{}] creating {}[{}]", new String[] {
					owner.getEntityType(), owner.getName(),
					newEntity.getEntityType(), newEntity.getName() });
		}

		newEntity.setOwner(owner);
		newEntity.enable();

		newEntity = entityDAO.createEntity(newEntity);

		if (owner != null) {
			newEntity.createRelationshipWith(owner, Role.OWNER, RelationshipStatus.ACTIVE);
			owner.createRelationshipWith(newEntity, Role.MEMBER, RelationshipStatus.ACTIVE);
		}

		return newEntity;
	}


	@Override
	public BaseEntity createEntity(BaseEntity newEntity, BaseEntity owner,
			BaseEntity creator) {

		newEntity = createEntity(newEntity, owner);

		if (creator != null && owner != null && creator.getId() != owner.getId()) {
			newEntity.createRelationshipWith(creator, Role.ADMINISTRATOR, RelationshipStatus.ACTIVE);
			creator.createRelationshipWith(newEntity, Role.MEMBER, RelationshipStatus.ACTIVE);
		}

		return newEntity;
	}


	/**
	 * @see au.com.handsunited.service.EntityService#updateEntity(au.com.handsunited.model.BaseEntity,
	 *      au.com.handsunited.model.BaseEntity)
	 */
	public BaseEntity updateEntity(BaseEntity entity, BaseEntity updatingEntity) {


		//	TODO chuck this isAdministratorOf() method call into an aspect maybe?

		if (updatingEntity.isAdministratorOf(entity)) {
			if (EntityServiceImpl.logger.isDebugEnabled()) {
				EntityServiceImpl.logger.debug("{}[{}] updating {}[{}].", new String[] {
						updatingEntity.getEntityType(),
						updatingEntity.getName(), entity.getEntityType(),
						entity.getName() });
			}

			entity.setLastModified(new JodaDateTimeWrapper(new DateTime()));
			entity = entityDAO.updateEntity(entity);

		} else {

			if (EntityServiceImpl.logger.isDebugEnabled()) {
				EntityServiceImpl.logger.debug(
						"{}[{}] has insufficient permissions to update{}[{}]",
						new String[] { updatingEntity.getEntityType(),
								updatingEntity.getName(),
								entity.getEntityType(), entity.getName() });
			}

		}


		return entity;
	}


	/**
	 * @see au.com.handsunited.service.EntityService#deleteEntity(au.com.handsunited.model.BaseEntity,
	 *      au.com.handsunited.model.BaseEntity)
	 */
	public void deleteEntity(BaseEntity entity, BaseEntity owner) {

		if (EntityServiceImpl.logger.isDebugEnabled()) {
			EntityServiceImpl.logger.debug("{}[{}] is attempting to delete {}[{}]", new String[] {
					owner.getEntityType(), owner.getName(),
					entity.getEntityType(), entity.getName() });
		}

		if (entity.getOwner() == owner) {

		} else {
			if (EntityServiceImpl.logger.isDebugEnabled()) {
				EntityServiceImpl.logger.debug("{}[{}] has insufficient powers to delete {}[{}]",
						new String[] { owner.getEntityType(), owner.getName(),
								entity.getEntityType(), entity.getName() });
			}
		}
	}


	/**
	 * @see au.com.handsunited.service.EntityService#postMessage(au.com.handsunited.model.BaseEntity,
	 *      au.com.handsunited.model.message.Message)
	 */
	@Override
	public BaseEntity postMessage(BaseEntity recipient, Message message) {
		recipient.getMessageBoard().addMessage(message);
		recipient.setMessageBoard(messageDAO.updateMessageBoard(recipient.getMessageBoard()));
		return recipient;
	}


	/**
	 *
	 * @param from
	 * @param name
	 * @param emailAddress
	 * @param contactName
	 * @param inviteeType
	 * @param message
	 */
	protected Invitation invitePotentialEntity (BaseEntity from, String name, String emailAddress,
			String contactName, String inviteeType, String message) {

		Invitation invitation =
			new Invitation(from, name, emailAddress, contactName, inviteeType,
					InvitationStatus.INVITED, message);

		invitation = invitationDAO.create(invitation);
		return invitation;
	}


	/**
	 *
	 * @param from
	 * @param emailAddress
	 * @param inviteeType
	 * @param message
	 */
	protected Invitation invitePotentialEntity (BaseEntity from, String emailAddress,
			String inviteeType, String message) {

		Invitation invitation = invitePotentialEntity(from, null, emailAddress, null, inviteeType, message);
		return invitation;
	}



	/**
	 *
	 * @param to
	 * @param from
	 * @param invitedEntities
	 * @param invitedEmailAddresses
	 * @param invitationMessage
	 * @return
	 */
	protected Map<String, List<Invitation>> inviteTo(BaseEntity to, BaseEntity from,
			List<BaseEntity> invitedEntities, String invitedEmailAddresses, String invitationMessage) {

		List<Invitation> validMemberInvitations = new ArrayList<Invitation>();
		List<Invitation> validNonMemberInvitations = new ArrayList<Invitation>();
		List<Invitation> invalidNonMemberInvitations = new ArrayList<Invitation>();

		//	Create invitations for the invited member entities
		for (BaseEntity baseEntity : invitedEntities) {
			Invitation invitation = new Invitation(
					from, to, baseEntity, InvitationStatus.INVITED, invitationMessage);
			invitation = invitationDAO.create(invitation);
			validMemberInvitations.add(invitation);
		}

		//	Create invitations for the invited non-member entities
		EmailAddressValidator emailAddressValidator = new EmailAddressValidator(invitedEmailAddresses);

		for (String emailAddress : emailAddressValidator.getValidEmailAddresses()) {
			Invitation invitation = new Invitation(
					from, to, emailAddress, Invitation.INVITEE_TYPE_USER, InvitationStatus.INVITED,
					invitationMessage);
			invitation = invitationDAO.create(invitation);
			validNonMemberInvitations.add(invitation);
		}

		for (String emailAddress : emailAddressValidator.getInvalidEmailAddresses()) {
			Invitation invitation = new Invitation(
					from, to, emailAddress, Invitation.INVITEE_TYPE_USER, InvitationStatus.INVITED,
					invitationMessage);

			// Dont call the dao to create as this is an invalid email address
			invalidNonMemberInvitations.add(invitation);
		}

		Map<String, List<Invitation>> invitationsMap = new HashMap<String, List<Invitation>>();
		invitationsMap.put(Invitation.INVITED_ENTITIES, validMemberInvitations);
		invitationsMap.put(Invitation.VALID_EMAILADDRESSES, validNonMemberInvitations);
		invitationsMap.put(Invitation.INVALID_EMAILADDRESSES, invalidNonMemberInvitations);

		return invitationsMap;

	}



	/**
	 * @see au.com.handsunited.service.EntityService#getEntity(java.lang.String)
	 */
	@Override
	public BaseEntity getEntity(String id) {
		return getEntity(Long.parseLong(id));
	}


	/**
	 * @see au.com.handsunited.service.EntityService#getEntity(java.lang.Long)
	 */
	@Override
	public BaseEntity getEntity(Long id) {
		return entityDAO.getEntity(id);
	}


	/**
	 * @see au.com.handsunited.dao.EntityDAO#getGroupsInNetworkFor(au.com.handsunited.model.BaseEntity, int, int)
	 */
	public List<Group> getGroupsInNetworkFor(BaseEntity entity, int firstResult, int maxResults) {
		return entityDAO.getGroupsInNetworkFor(entity, firstResult, maxResults);
	}

	/**
	 * @see au.com.handsunited.dao.EntityDAO#getGroupRelationshipsFor(au.com.handsunited.model.BaseEntity, int, int)
	 */
	public List<Relationship> getGroupRelationshipsFor(BaseEntity entity, int firstResult, int maxResults) {
		return entityDAO.getGroupRelationshipsFor(entity, firstResult, maxResults);
	}

	/**
	 * @see au.com.handsunited.dao.EntityDAO#getOrganisationsInNetworkFor(au.com.handsunited.model.BaseEntity, int, int)
	 */
	public List<Organisation> getOrganisationsInNetworkFor(BaseEntity entity, int firstResult, int maxResults) {
		return entityDAO.getOrganisationsInNetworkFor(entity, firstResult, maxResults);
	}


	/**
	 * @see au.com.handsunited.dao.EntityDAO#getOrganisationRelationshipsFor(au.com.handsunited.model.BaseEntity, int, int)
	 */
	public List<Relationship> getOrganisationRelationshipsFor(BaseEntity entity, int firstResult, int maxResults) {
		return entityDAO.getOrganisationRelationshipsFor(entity, firstResult, maxResults);
	}


	/**
	 * @see au.com.handsunited.dao.EntityDAO#getProjectsInNetworkFor(au.com.handsunited.model.BaseEntity, int, int)
	 */
	public List<Project> getProjectsInNetworkFor(BaseEntity entity, int firstResult, int maxResults) {
		return entityDAO.getProjectsInNetworkFor(entity, firstResult, maxResults);
	}

	/**
	 * @see au.com.handsunited.dao.EntityDAO#getProjectRelationshipsFor(au.com.handsunited.model.BaseEntity, int, int)
	 */
	public List<Relationship> getProjectRelationshipsFor(BaseEntity entity, int firstResult, int maxResults) {
		return entityDAO.getProjectRelationshipsFor(entity, firstResult, maxResults);
	}

	/**
	 * @see au.com.handsunited.dao.EntityDAO#getUsersInNetworkFor(au.com.handsunited.model.BaseEntity, int, int)
	 */
	public List<User> getUsersInNetworkFor(BaseEntity entity, int firstResult, int maxResults) {
		return entityDAO.getUsersInNetworkFor(entity, firstResult, maxResults);
	}

	/**
	 * @see au.com.handsunited.dao.EntityDAO#getUserRelationshipsFor(au.com.handsunited.model.BaseEntity, int, int)
	 */
	public List<Relationship> getUserRelationshipsFor(BaseEntity entity, int firstResult, int maxResults) {
		return entityDAO.getUserRelationshipsFor(entity, firstResult, maxResults);
	}




	protected boolean addMemberToNetwork(BaseEntity entity, BaseEntity member,
			Role entityRole, Role memberRole, RelationshipStatus entityRelationshipStatus,
			RelationshipStatus memberRelationshipStatus) {

		Relationship relationship1 = entity.createRelationshipWith(member, entityRole, entityRelationshipStatus);
		Relationship relationship2 = member.createRelationshipWith(entity, memberRole, memberRelationshipStatus);

		if (relationship1 == null || relationship2 == null) {
			if (EntityServiceImpl.logger.isDebugEnabled()) {
			EntityServiceImpl.logger.debug("{}[{}] is already a member of {}[{}]",
					new String[] { member.getEntityType(), member.getName(), entity.getEntityType(), entity.getName() });
			}
			return false;
		}

		entity = entityDAO.updateEntity(entity);
		member = entityDAO.updateEntity(entity);

		if (EntityServiceImpl.logger.isDebugEnabled()) {
			EntityServiceImpl.logger.debug("{} [{}] added as a member to {}[{}]",
					new String[] { member.getEntityType(), member.getName(), entity.getEntityType(), entity.getName() });
		}

		return true;
	}


	protected boolean removeMemberFromNetwork(BaseEntity entity, BaseEntity member) {

		if (member != null) {

			if (EntityServiceImpl.logger.isDebugEnabled()) {
				EntityServiceImpl.logger.debug("Removing {}[{}] from {}'s {}-Network.",
						new String[] { member.getEntityType(), member.getName(), entity.getName() });
			}

			entity.removeRelationshipWith(member);
			entity = entityDAO.updateEntity(entity);
			member.removeRelationshipWith(entity);
			member = entityDAO.updateEntity(member);

			return true;

		} else {

			if (EntityServiceImpl.logger.isDebugEnabled()) {
				EntityServiceImpl.logger.debug("No entity with id[{}] found.", member);
			}
			return false;
		}
	}


	public boolean removeMemberFromNetwork(BaseEntity entity, String memberId) {

		BaseEntity member = getEntity(memberId);
		return removeMemberFromNetwork(entity, member);
	}


	/**
	 * @return the entityDAO
	 */
	public EntityDAO getEntityDAO() {
		return entityDAO;
	}


	public MessageDAO getMessageDAO() {
		return messageDAO;
	}

}
